﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;

namespace GBricks.Collections
{
  partial class Comparers
  {
    public static IEqualityComparer<HashSet<T>> HashSetDefaultComparer<T>() {
      Contract.Ensures(Contract.Result<IEqualityComparer<HashSet<T>>>() != null);
      return HashSetDefaultEqualityComparer<T>.Default;
    }

    public static IEqualityComparer<HashSet<T>> HashSetCustomComparer<T>(IEqualityComparer<T> comparer = null) {
      Contract.Ensures(Contract.Result<IEqualityComparer<HashSet<T>>>() != null);
      return new HashSetCustomEqualityComparer<T>(comparer);
    }

    [Serializable]
    private sealed class HashSetDefaultEqualityComparer<T> : EqualityComparer<HashSet<T>>
    {
      private const int MagicNumber = 13;

      private static readonly HashSetDefaultEqualityComparer<T> Instance = new HashSetDefaultEqualityComparer<T>();

      private HashSetDefaultEqualityComparer() { }

      public static new HashSetDefaultEqualityComparer<T> Default {
        [DebuggerStepThrough]
        get {
          Contract.Ensures(Contract.Result<HashSetDefaultEqualityComparer<T>>() != null);
          return Instance;
        }
      }

      public override bool Equals(HashSet<T> x, HashSet<T> y) {
        if(x == null) {
          return y == null;
        } else if(y == null) {
          return false;
        } else if(!Equals(x.Comparer, y.Comparer)) {
          return false;
        }//if

        return x.SetEquals(y);
      }

      public override int GetHashCode(HashSet<T> obj) {
        if(obj == null) {
          return 0;
        }//if

        return obj.Aggregate(0, (hash, item) => hash ^ (obj.Comparer.GetHashCode(item) & 0x7FFFFFFF));
      }

      public override bool Equals(object obj) {
        return obj is HashSetDefaultEqualityComparer<T>;
      }

      public override int GetHashCode() {
        return MagicNumber;
      }
    }

    [Serializable]
    private sealed class HashSetCustomEqualityComparer<T> : EqualityComparer<HashSet<T>>
    {
      public HashSetCustomEqualityComparer(IEqualityComparer<T> comparer = null) {
        Comparer = comparer ?? EqualityComparer<T>.Default;
      }

      private IEqualityComparer<T> Comparer { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparer != null);
      }

      public override bool Equals(HashSet<T> x, HashSet<T> y) {
        if(x == null) {
          return y == null;
        } else if(y == null) {
          return false;
        } else if(Equals(x.Comparer, y.Comparer) && Equals(x.Comparer, Comparer)) {
          return x.SetEquals(y);
        }//if

        var set = new HashSet<T>(x, Comparer);
        return set.SetEquals(y);
      }

      public override int GetHashCode(HashSet<T> obj) {
        if(obj == null) {
          return 0;
        }//if

        return obj.Aggregate(0, (hash, item) =>
          hash ^ (Comparer.GetHashCode(item) & 0x7FFFFFFF));
      }

      public override bool Equals(object obj) {
        var other = obj as HashSetCustomEqualityComparer<T>;
        return other != null && Equals(other.Comparer, Comparer);
      }

      public override int GetHashCode() {
        return Comparer.GetHashCode();
      }
    }
  }
}
